
#include <typeinfo>
#include "../aprtools/APRFile.h"
#include "../net/netgeneral.h"
#include "../utils/Hash.h"
#include "../utils/StringUtils.h"
#include "../iface/APRInterface.h"
#include "../iface/testerror.h"
#include "HTTPServer.h"
#include "BrokerMessage.h"

/** Add a key to a constructed 'keyslot' on the broker message.
 * 
 * @param keyslot_number The number of the key slot to use.
 * 
 * @param keyname The name of the key to add.
 * 
 * @param ownpwd Use the password given to decrypt the key in the
 * database.
 * 
 * @param tpurpose The type of key to use. Many keys can go in one
 * keyslot if they have different uses.
 * 
 * @param kdb The key database. */
void BrokerMessage::insertKeyToSend(int keyslot_number, const char *keyname,
   const char *ownpwd, Key::keypurposetype tpurpose, KeyDB *kdb)
{
   const KeyTableView *ktable = NULL;
   const char *keytname = "";
   switch (tpurpose)
   {
      case Key::KEYPURPOSE_ENCRYPTION:
         ktable = kdb->encryptionTable(); keytname = "enc"; break;
      case Key::KEYPURPOSE_DECRYPTION:
         ktable = kdb->decryptionTable(); keytname = "dec"; break;
      case Key::KEYPURPOSE_SIGNING:
         ktable = kdb->signingTable(); keytname = "sgn"; break;
      case Key::KEYPURPOSE_CHECKING:
         ktable = kdb->checkingTable(); keytname = "chk"; break;
   }
   
   Key t_abs_key(kdb, keyname, tpurpose);
   AbstractKey *abskey = t_abs_key.getKey((ownpwd) ? (ownpwd) : (ktable->getWIPPassword()));
   if (abskey)
   {
      addHeaderLineNOCRLF("keyslot%d_%skeyname: %s", keyslot_number, keytname, keyname);
      addHeaderLineNOCRLF("keyslot%d_%skeytype: %s", keyslot_number, keytname, abskey->getKeyTypeAbbrName());
      addHeaderLineNOCRLF("keyslot%d_%skeymark: %s", keyslot_number, keytname, t_abs_key.getKeyMark());
      if (ownpwd)
         addHeaderLineNOCRLF("keyslot%d_%skeypwd: %s", keyslot_number, keytname, ownpwd);
      char *keytextdata = NULL;
      int retval = abskey->exportKeyToASCII(&keytextdata);
      if (retval != 0)
         errprint("key data export failed");
      if (keytextdata)
      {
         int keyentry = addVerbatimPayloadString(keytextdata);
         addHeaderLineNOCRLF("keyslot%d_%skeytextpayload: %d", keyslot_number, keytname, keyentry);
      }
   }
}

/** Obtain an abstract key from a 'keyslot'.
 * 
 * @param keyslot_number The number of the key slot to use.
 * 
 * @param tpurpose Key purpose to get the key from the key slot of the
 * broker message.
 * 
 * @param kdb Key database to use.
 * 
 * @return The abstract key, if valid. */
AbstractKey *BrokerMessage::obtainAbstractKeyFromReceived(int keyslot_number, Key::keypurposetype tpurpose, KeyDB *kdb)
{
   const KeyTableView *ktable = NULL;
   const char *keytname = "";
   switch (tpurpose)
   {
      case Key::KEYPURPOSE_ENCRYPTION:
         ktable = kdb->encryptionTable(); keytname = "enc"; break;
      case Key::KEYPURPOSE_DECRYPTION:
         ktable = kdb->decryptionTable(); keytname = "dec"; break;
      case Key::KEYPURPOSE_SIGNING:
         ktable = kdb->signingTable(); keytname = "sgn"; break;
      case Key::KEYPURPOSE_CHECKING:
         ktable = kdb->checkingTable(); keytname = "chk"; break;
   }

   Key::keyopenerror errval;
   AbstractKey *abskey = NULL;
   const APRTable *htable = getHeaderTable();
   const char *abs_key_name = htable->get("keyslot%d_%skeyname", keyslot_number, keytname);
   const char *abs_key_pwd = htable->get("keyslot%d_%skeypwd", keyslot_number, keytname);
   const char *abs_key_type = htable->get("keyslot%d_%skeytype", keyslot_number, keytname);
   const char *abs_key_mark = htable->get("keyslot%d_%skeymark", keyslot_number, keytname);
   const char *abs_key_textpay = htable->get("keyslot%d_%skeytextpayload", keyslot_number, keytname);
   const char *abs_key_text = (abs_key_textpay) ? getPayload(atoi(abs_key_textpay)) : NULL;
   if (abs_key_text)
      abskey = Key::tryToGetKeyFromText(abs_key_type, abs_key_pwd, abs_key_text, abs_key_name, &errval);
   return abskey;
}

/** A key from a constructed 'keyslot'.
 * 
 * @param keyslot_number The number of the key slot to use.
 * 
 * @param addmarks Marks to add to the set of key marks. Each is added
 * once, to a maximum of one. Marking multiple times is not allowed
 * here.
 * 
 * @param removemarks Marks to remove from the set of key marks.
 * 
 * @param tpurpose Key purpose to get the key from the key slot of the
 * broker message.
 * 
 * @param owner The owner for the key (can be NULL).
 * 
 * @param useownpwd Use the password given in the message, or the WIP
 * password to encrypt the key in the database.
 * 
 * @param override_name Override the name given to the key by the
 * client with this name.
 * 
 * @param kdb The key database.
 * 
 * @return Zero when the key is inserted, non-zero if it is not. */
int BrokerMessage::obtainKeyFromReceived(int keyslot_number, const char *addmarks,
   const char *removemarks, Key::keypurposetype tpurpose, const char *owner,
   bool useownpwd, const char *override_name, KeyDB *kdb)
{
   SQLiteAutoLock thislock(kdb->getAutoLock());
   const KeyTableView *ktable = NULL;
   const char *keytname = "";
   switch (tpurpose)
   {
      case Key::KEYPURPOSE_ENCRYPTION:
         ktable = kdb->encryptionTable(); keytname = "enc"; break;
      case Key::KEYPURPOSE_DECRYPTION:
         ktable = kdb->decryptionTable(); keytname = "dec"; break;
      case Key::KEYPURPOSE_SIGNING:
         ktable = kdb->signingTable(); keytname = "sgn"; break;
      case Key::KEYPURPOSE_CHECKING:
         ktable = kdb->checkingTable(); keytname = "chk"; break;
   }

   Key::keyopenerror errval;
   AbstractKey *abskey = NULL;
   if (!kdb)
   {
      errprint("key database is null");
      return 1;
   }
   const APRTable *htable = getHeaderTable();
   const char *abs_key_name = (override_name) ? override_name : htable->get("keyslot%d_%skeyname", keyslot_number, keytname);
   const char *abs_key_pwd = htable->get("keyslot%d_%skeypwd", keyslot_number, keytname);
   const char *abs_key_type = htable->get("keyslot%d_%skeytype", keyslot_number, keytname);
   const char *abs_key_mark = htable->get("keyslot%d_%skeymark", keyslot_number, keytname);
   const char *abs_key_textpay = htable->get("keyslot%d_%skeytextpayload", keyslot_number, keytname);
   const char *abs_key_text = (abs_key_textpay) ? getPayload(atoi(abs_key_textpay)) : NULL;
   if (abs_key_name == NULL)
      abs_key_name = owner;
   int absexists = ktable->testKeyExists(abs_key_name);
   if (absexists) { errprint("cryptography '%s' key '%s' exists in the table!", keytname, abs_key_name); return 1; }
   if (abs_key_text) abskey = Key::tryToGetKeyFromText(abs_key_type, abs_key_pwd, abs_key_text, abs_key_name, &errval);
   if (errval)
      Key::showKeyOpenError(abs_key_name, tpurpose, errval);
   if (abskey == NULL)
   {
      errprint("could not get '%s' key", keytname);
      return 1;
   }

   switch (tpurpose)
   {
      case Key::KEYPURPOSE_ENCRYPTION:
      {
         try
         {
            Key t_enc_key(kdb, *(dynamic_cast<EncryptionKey *>(abskey)), (useownpwd) ? abs_key_pwd : (ktable->getWIPPassword()));
            t_enc_key.setKeyMark(abs_key_mark);
            if (addmarks) for (int z = 0; z < strlen(addmarks); z++) t_enc_key.addKeyMark(addmarks[z], 1);
            if (removemarks) for (int z = 0; z < strlen(removemarks); z++) t_enc_key.removeKeyMark(removemarks[z]);
            t_enc_key.setOwner(owner);
            t_enc_key.commit();
         }
         catch (std::bad_cast &bc)
         {
            errprint("invalid encryption key '%s'", abs_key_name);
         }
         break;
      }
      case Key::KEYPURPOSE_DECRYPTION:
      {
         try
         {
            Key t_dec_key(kdb, *(dynamic_cast<DecryptionKey *>(abskey)), (useownpwd) ? abs_key_pwd : (ktable->getWIPPassword()));
            t_dec_key.setKeyMark(abs_key_mark);
            if (addmarks) for (int z = 0; z < strlen(addmarks); z++) t_dec_key.addKeyMark(addmarks[z], 1);
            if (removemarks) for (int z = 0; z < strlen(removemarks); z++) t_dec_key.removeKeyMark(removemarks[z]);
            t_dec_key.setOwner(owner);
            t_dec_key.commit();
         }
         catch (std::bad_cast &bc)
         {
            errprint("invalid decryption key '%s'", abs_key_name);
         }
         break;
      }
      case Key::KEYPURPOSE_SIGNING:
      {
         try
         {
            Key t_sgn_key(kdb, *(dynamic_cast<SigningKey *>(abskey)), (useownpwd) ? abs_key_pwd : (ktable->getWIPPassword()));
            t_sgn_key.setKeyMark(abs_key_mark);
            if (addmarks) for (int z = 0; z < strlen(addmarks); z++) t_sgn_key.addKeyMark(addmarks[z], 1);
            if (removemarks) for (int z = 0; z < strlen(removemarks); z++) t_sgn_key.removeKeyMark(removemarks[z]);
            t_sgn_key.setOwner(owner);
            t_sgn_key.commit();
         }
         catch (std::bad_cast &bc)
         {
            errprint("invalid signing key '%s'", abs_key_name);
         }
         break;
      }
      case Key::KEYPURPOSE_CHECKING:
      {
         try
         {
            Key t_chk_key(kdb, *(dynamic_cast<CheckingKey *>(abskey)), (useownpwd) ? abs_key_pwd : (ktable->getWIPPassword()));
            t_chk_key.setKeyMark(abs_key_mark);
            if (addmarks) for (int z = 0; z < strlen(addmarks); z++) t_chk_key.addKeyMark(addmarks[z], 1);
            if (removemarks) for (int z = 0; z < strlen(removemarks); z++) t_chk_key.removeKeyMark(removemarks[z]);
            t_chk_key.setOwner(owner);
            t_chk_key.commit();
         }
         catch (std::bad_cast &bc)
         {
            errprint("invalid checking key '%s'", abs_key_name);
         }
         break;
      }
   }

   if (abskey) delete abskey;
   return 0;
}

/** Add all keys of this identity to a constructed 'keyslot' on the
 * broker message.
 * 
 * @param keyslot_number The number of the key slot to use.
 * 
 * @param keyname The name of the key to add.
 * 
 * @param ownpwd Use the password given to decrypt the key in the
 * database.
 * 
 * @param kdb The key database. */
void BrokerMessage::insertAllKeysToSend(int keyslot_number, const char *keyname,
   const char *ownpwd, KeyDB *kdb)
{
   insertKeyToSend(keyslot_number, keyname, ownpwd, Key::KEYPURPOSE_ENCRYPTION, kdb);
   insertKeyToSend(keyslot_number, keyname, ownpwd, Key::KEYPURPOSE_DECRYPTION, kdb);
   insertKeyToSend(keyslot_number, keyname, ownpwd, Key::KEYPURPOSE_SIGNING, kdb);
   insertKeyToSend(keyslot_number, keyname, ownpwd, Key::KEYPURPOSE_CHECKING, kdb);
}

/** Add the freely available (usually public) keys of this identity to
 * a constructed 'keyslot' on the broker message.
 * 
 * @param keyslot_number The number of the key slot to use.
 * 
 * @param keyname The name of the key to add.
 * 
 * @param ownpwd Use the password given to decrypt the key in the
 * database.
 * 
 * @param kdb The key database. */
void BrokerMessage::insertAvailableKeysToSend(int keyslot_number, const char *keyname,
   const char *ownpwd, KeyDB *kdb)
{
   insertKeyToSend(keyslot_number, keyname, ownpwd, Key::KEYPURPOSE_ENCRYPTION, kdb);
   insertKeyToSend(keyslot_number, keyname, ownpwd, Key::KEYPURPOSE_CHECKING, kdb);
}

/** Add the secret (usually private) keys of this identity to a
 * constructed 'keyslot' on the broker message.
 * 
 * @param keyslot_number The number of the key slot to use.
 * 
 * @param keyname The name of the key to add.
 * 
 * @param ownpwd Use the password given to decrypt the key in the
 * database.
 * 
 * @param kdb The key database. */
void BrokerMessage::insertSecretKeysToSend(int keyslot_number, const char *keyname,
   const char *ownpwd, KeyDB *kdb)
{
   insertKeyToSend(keyslot_number, keyname, ownpwd, Key::KEYPURPOSE_DECRYPTION, kdb);
   insertKeyToSend(keyslot_number, keyname, ownpwd, Key::KEYPURPOSE_SIGNING, kdb);
}

/** Add the SSH key pair of this identity to a constructed 'keyslot'
 * on the broker message.
 * 
 * @param keyslot_number The number of the key slot to use.
 * 
 * @param keyname The name of the key to add.
 * 
 * @param ownpwd Use the password given to decrypt the key in the
 * database.
 * 
 * @param kdb The key database. */
void BrokerMessage::insertSSHKeysToSend(int keyslot_number, const char *keyname,
   const char *ownpwd, KeyDB *kdb)
{
   insertKeyToSend(keyslot_number, keyname, ownpwd, Key::KEYPURPOSE_SIGNING, kdb);
   insertKeyToSend(keyslot_number, keyname, ownpwd, Key::KEYPURPOSE_CHECKING, kdb);
}

/** Get a complete set of keys from a constructed 'keyslot'.
 * Revert all keys if any fail, it is assumed that an actor identity is
 * involved here.
 * 
 * @param keyslot_number The number of the key slot to use.
 * 
 * @param addmarks Marks to add to the set of key marks. Each is added
 * once, to a maximum of one. Marking multiple times is not allowed
 * here.
 * 
 * @param removemarks Marks to remove from the set of key marks.
 * 
 * @param owner The owner for the key (can be NULL).
 * 
 * @param useownpwd Use the password given in the message to encrypt
 * the key in the database.
 * 
 * @param override_name Override the name given to the key by the
 * client with this name.
 * 
 * @param kdb The key database.
 * 
 * @return Zero when the keys are inserted, non-zero if they are
 * not. */
int BrokerMessage::obtainAllKeysFromReceived(int keyslot_number,
   const char *addmarks, const char *removemarks, const char *owner,
   bool useownpwd, const char *override_name, KeyDB *kdb)
{
   SQLiteAutoLock thislock(kdb->getAutoLock());
   int retval = 0;
   retval |=  (obtainKeyFromReceived(keyslot_number, addmarks, removemarks,
      Key::KEYPURPOSE_ENCRYPTION, owner, useownpwd, override_name, kdb) != 0);
   retval |= ((obtainKeyFromReceived(keyslot_number, addmarks, removemarks,
      Key::KEYPURPOSE_DECRYPTION, owner, useownpwd, override_name, kdb) != 0) << 1);
   retval |= ((obtainKeyFromReceived(keyslot_number, addmarks, removemarks,
      Key::KEYPURPOSE_SIGNING, owner, useownpwd, override_name, kdb) != 0) << 2);
   retval |= ((obtainKeyFromReceived(keyslot_number, addmarks, removemarks,
      Key::KEYPURPOSE_CHECKING, owner, useownpwd, override_name, kdb) != 0) << 3);
   if (retval != 0)
   {
      // revert any keys with zero attributes - ones that were successful
      if ((retval & 0x1) == 0)
         kdb->encryptionTable()->deleteKey(htable->get("keyslot%d_%skeyname", keyslot_number, "enc"));
      if ((retval & 0x2) == 0)
         kdb->decryptionTable()->deleteKey(htable->get("keyslot%d_%skeyname", keyslot_number, "dec"));
      if ((retval & 0x4) == 0)
         kdb->signingTable()->deleteKey(htable->get("keyslot%d_%skeyname", keyslot_number, "sgn"));
      if ((retval & 0x8) == 0)
         kdb->checkingTable()->deleteKey(htable->get("keyslot%d_%skeyname", keyslot_number, "chk"));
   }
   return (retval != 0);
}

/** Get the available (usually public) set of keys from a constructed
 * 'keyslot'. Revert all keys if any fail, it is assumed that an actor identity is
 * involved here.
 * 
 * @param keyslot_number The number of the key slot to use.
 * 
 * @param addmarks Marks to add to the set of key marks. Each is added
 * once, to a maximum of one. Marking multiple times is not allowed
 * here.
 * 
 * @param removemarks Marks to remove from the set of key marks.
 * 
 * @param owner The owner for the key (can be NULL).
 * 
 * @param useownpwd Use the password given in the message to encrypt
 * the key in the database.
 * 
 * @param override_name Override the name given to the key by the
 * client with this name.
 * 
 * @param kdb The key database.
 * 
 * @return Zero when the keys are inserted, non-zero if they are
 * not. */
int BrokerMessage::obtainAvailableKeysFromReceived(int keyslot_number,
   const char *addmarks, const char *removemarks, const char *owner,
   bool useownpwd, const char *override_name, KeyDB *kdb)
{
   SQLiteAutoLock thislock(kdb->getAutoLock());
   int retval = 0;
   retval |=  (obtainKeyFromReceived(keyslot_number, addmarks, removemarks,
      Key::KEYPURPOSE_ENCRYPTION, owner, useownpwd, override_name, kdb) != 0);
   retval |= ((obtainKeyFromReceived(keyslot_number, addmarks, removemarks,
      Key::KEYPURPOSE_CHECKING, owner, useownpwd, override_name, kdb) != 0) << 1);
   if (retval != 0)
   {
      // revert any keys with zero attributes - ones that were successful
      if ((retval & 0x1) == 0)
         kdb->encryptionTable()->deleteKey(htable->get("keyslot%d_%skeyname", keyslot_number, "enc"));
      if ((retval & 0x2) == 0)
         kdb->checkingTable()->deleteKey(htable->get("keyslot%d_%skeyname", keyslot_number, "chk"));
   }
   return (retval != 0);
}

/** Get the secret (usually private) set of keys from a constructed
 * 'keyslot'.
 * Revert all keys if any fail, it is assumed that an actor identity is
 * involved here.
 * 
 * @param keyslot_number The number of the key slot to use.
 * 
 * @param addmarks Marks to add to the set of key marks. Each is added
 * once, to a maximum of one. Marking multiple times is not allowed
 * here.
 * 
 * @param removemarks Marks to remove from the set of key marks.
 * 
 * @param owner The owner for the key (can be NULL).
 * 
 * @param useownpwd Use the password given in the message to encrypt
 * the key in the database.
 * 
 * @param override_name Override the name given to the key by the
 * client with this name.
 * 
 * @param kdb The key database.
 * 
 * @return Zero when the keys are inserted, non-zero if they are
 * not. */
int BrokerMessage::obtainSecretKeysFromReceived(int keyslot_number,
   const char *addmarks, const char *removemarks, const char *owner,
   bool useownpwd, const char *override_name, KeyDB *kdb)
{
   SQLiteAutoLock thislock(kdb->getAutoLock());
   int retval = 0;
   retval |=  (obtainKeyFromReceived(keyslot_number, addmarks, removemarks,
      Key::KEYPURPOSE_DECRYPTION, owner, useownpwd, override_name, kdb) != 0);
   retval |= ((obtainKeyFromReceived(keyslot_number, addmarks, removemarks,
      Key::KEYPURPOSE_SIGNING, owner, useownpwd, override_name, kdb) != 0) << 1);
   if (retval != 0)
   {
      // revert any keys with zero attributes - ones that were successful
      if ((retval & 0x1) == 0)
         kdb->decryptionTable()->deleteKey(htable->get("keyslot%d_%skeyname", keyslot_number, "dec"));
      if ((retval & 0x2) == 0)
         kdb->signingTable()->deleteKey(htable->get("keyslot%d_%skeyname", keyslot_number, "sgn"));
   }
   return (retval != 0);
}

/** Get the SSH keypair from a constructed 'keyslot'.
 * Revert all keys if any fail, it is assumed that an actor identity is
 * involved here.
 * 
 * @param keyslot_number The number of the key slot to use.
 * 
 * @param addmarks Marks to add to the set of key marks. Each is added
 * once, to a maximum of one. Marking multiple times is not allowed
 * here.
 * 
 * @param removemarks Marks to remove from the set of key marks.
 * 
 * @param owner The owner for the key (can be NULL).
 * 
 * @param useownpwd Use the password given in the message to encrypt
 * the key in the database.
 * 
 * @param override_name Override the name given to the key by the
 * client with this name.
 * 
 * @param kdb The key database.
 * 
 * @return Zero when the keys are inserted, non-zero if they are
 * not. */
int BrokerMessage::obtainSSHKeysFromReceived(int keyslot_number,
   const char *addmarks, const char *removemarks, const char *owner,
   bool useownpwd, const char *override_name, KeyDB *kdb)
{
   SQLiteAutoLock thislock(kdb->getAutoLock());
   int retval = 0;
   retval |=  (obtainKeyFromReceived(keyslot_number, addmarks, removemarks,
      Key::KEYPURPOSE_SIGNING, owner, useownpwd, override_name, kdb) != 0);
   retval |= ((obtainKeyFromReceived(keyslot_number, addmarks, removemarks,
      Key::KEYPURPOSE_CHECKING, owner, useownpwd, override_name, kdb) != 0) << 1);
   if (retval != 0)
   {
      // revert any keys with zero attributes - ones that were successful
      if ((retval & 0x1) == 0)
         kdb->signingTable()->deleteKey(htable->get("keyslot%d_%skeyname", keyslot_number, "sgn"));
      if ((retval & 0x2) == 0)
         kdb->checkingTable()->deleteKey(htable->get("keyslot%d_%skeyname", keyslot_number, "chk"));
   }
   return (retval != 0);
}

/** Write a payload out to a file.
 * 
 * @param payload_number The number of the payload to write out.
 * 
 * @param iformatfname The file name format string.
 * 
 * @return Non-zero on error, zero on success. */
int BrokerMessage::writePayloadOutToExistingFile(int payload_number, const char *iformatfname, ...)
{
   int result = 0;
   va_list args; va_list newcopy;
   va_start(args, iformatfname); va_copy(newcopy, args);
   const char *fname = StringUtils::mpprintf_va(mp, iformatfname, newcopy);
   va_end(newcopy); va_end(args);
   apr_status_t status;
   apr_file_t *newFP;
   status = APRFile::openWrapper(&newFP, fname, APR_FOPEN_WRITE|APR_FOPEN_BINARY|APR_FOPEN_BUFFERED, APR_OS_DEFAULT, mp);
   if (status == 0)
   {
      apr_size_t cesize = getPayloadLength(payload_number);
      status = apr_file_write_full(newFP, getPayload(payload_number), cesize, &cesize);
      CheckAPRError(status);
      status = apr_file_close(newFP);
      CheckAPRError(status);
      return (cesize == getPayloadLength(payload_number));
   }
   else
   {
      CheckAPRError(status);
      errprint("problem opening file '%s'", fname);
   }
   return 0;
}

/** Write a payload out to a file (but overwrite any existing file).
 * 
 * @param payload_number The number of the payload to write out.
 * 
 * @param iformatfname The file name format string.
 * 
 * @return Non-zero on error, zero on success. */
int BrokerMessage::writePayloadOutToOverwriteFile(int payload_number, const char *iformatfname, ...)
{
   int result = 0;
   va_list args; va_list newcopy;
   va_start(args, iformatfname); va_copy(newcopy, args);
   const char *fname = StringUtils::mpprintf_va(mp, iformatfname, newcopy);
   va_end(newcopy); va_end(args);
   apr_status_t status;
   apr_file_t *newFP;
   status = APRFile::openWrapper(&newFP, fname, APR_FOPEN_CREATE|APR_FOPEN_WRITE|APR_FOPEN_BINARY|APR_FOPEN_BUFFERED, APR_OS_DEFAULT, mp);
   if (status == 0)
   {
      apr_size_t cesize = getPayloadLength(payload_number);
      status = apr_file_write_full(newFP, getPayload(payload_number), cesize, &cesize);
      CheckAPRError(status);
      status = apr_file_close(newFP);
      CheckAPRError(status);
      return (cesize == getPayloadLength(payload_number));
   } else errprint("problem opening file '%s'", fname);
   return 0;
}

/** Write a payload out to a file (but remove, rewrite and replace any
 * existing file).
 * 
 * @param payload_number The number of the payload to write out.
 * 
 * @param iformatfname The file name format string.
 * 
 * @return Non-zero on error, zero on success. */
int BrokerMessage::writePayloadOutToReplaceFile(int payload_number, const char *iformatfname, ...)
{
   int result = 0;
   va_list args; va_list newcopy;
   va_start(args, iformatfname); va_copy(newcopy, args);
   const char *fname = StringUtils::mpprintf_va(mp, iformatfname, newcopy);
   va_end(newcopy); va_end(args);
   apr_status_t status;
   apr_file_t *newFP;
   status = APRFile::openWrapper(&newFP, fname, APR_FOPEN_TRUNCATE|APR_FOPEN_CREATE|APR_FOPEN_WRITE|APR_FOPEN_BINARY|APR_FOPEN_BUFFERED, APR_OS_DEFAULT, mp);
   if (status == 0)
   {
      apr_size_t cesize = getPayloadLength(payload_number);
      status = apr_file_write_full(newFP, getPayload(payload_number), cesize, &cesize);
      CheckAPRError(status);
      status = apr_file_close(newFP);
      CheckAPRError(status);
      return (cesize == getPayloadLength(payload_number));
   } else errprint("problem opening file '%s'", fname);
   return 0;
}

/** Attach the SHA1 hash for the file and then the file in separate payloads.
 * 
 * @param iformatfname The filename format string.
 * 
 * @return The size of the file attached in bytes. */
apr_size_t BrokerMessage::attachHashAndThenFile(const char *iformatfname, ...)
{
   va_list args; va_list newcopy;
   va_start(args, iformatfname); va_copy(newcopy, args);
   const char *fname = StringUtils::mpprintf_va(mp, iformatfname, newcopy);
   va_end(newcopy); va_end(args);
   apr_status_t status;
   apr_file_t *newFP;
   status = APRFile::openWrapper(&newFP, fname, APR_FOPEN_READ|APR_FOPEN_BINARY|APR_FOPEN_BUFFERED, APR_OS_DEFAULT, mp);
   CheckAPRError(status);
   apr_finfo_t finfo;
#ifdef USE_STAT_FILE_SIZE
   status = apr_file_info_get(&finfo, APR_FINFO_SIZE, newFP);
   CheckAPRError(status);
#else
   apr_off_t offset = 0;
   status = apr_file_seek(newFP, APR_END, &offset);
   CheckAPRError(status);
   finfo.size = offset;
   offset = 0;
   status = apr_file_seek(newFP, APR_SET, &offset);
   CheckAPRError(status);
#endif
   if (finfo.size == 0)
   {
      return 0;
   }
   apr_size_t fullread = finfo.size;
   char *encd = (char *)malloc(sizeof(char)*(finfo.size));
   status = apr_file_read_full(newFP, encd, finfo.size, &fullread);
   CheckAPRError(status);
   char *phash = Hash::getHexSHA1Hash(encd, fullread, NULL);
   addVerbatimPayloadString(phash);
   free(phash);
   if (fullread != finfo.size)
   {
      errprint("expected to read %" APR_OFF_T_FMT " bytes, actually read %" APR_SIZE_T_FMT, finfo.size, fullread);
      addVerbatimPayloadString("");
   } else
   {
      addPayloadBuffer(encd, finfo.size);
   }
   free(encd);
   return finfo.size;
}

/** Send the encrypted message by serialising onto the given bucket brigade
 * 
 * @param bb The bucket brigade to pipe the data out on.
 * 
 * @param kdb The key database.
 * 
 * @return Non-zero on error, zero on success. */
int BrokerMessage::sendEncryptedOnBucketBrigade(APRBucketBrigade *bb, KeyDB *kdb)
{
   FATAL_IF_BLANK(1, recipient, "recipient");
   FATAL_IF_BLANK(1, sender, "sender");
   FATAL_IF_BLANK(1, messagetype, "message type");
   FATAL_IF_BLANK(1, processee, "processee");
   EncryptWrapper *encwrap_content = kdb->getEncrypter(recipientkey, (willsign) ? senderkey : NULL, true);
   FATAL_IF_NULL(1, encwrap_content, "encryption wrapper for the message content");
   std::vector<char *> *headers_to_be_encrypted = new std::vector<char *>();
   char *mpoold_header_stream = getHeaderStream();
   char *mallocd_header_stream = strdup(mpoold_header_stream);
   headers_to_be_encrypted->push_back(mallocd_header_stream);
   char *payload_count_header =
      StringUtils::varprintf("Payload-Count: %d" CRLF_STR, (int)((payloads) ? payloads->size() : 0));
   headers_to_be_encrypted->push_back(payload_count_header);
   apr_off_t sum_of_payloads_lengths = 0;
   if (payloads)
   {
      for (int i = 0; i < payloads->size(); i++)
      {
         char *payload_content_length_header =
            StringUtils::varprintf("Payload%d-Content-Length: %d" CRLF_STR, i, (int)(lengths->at(i)));
         headers_to_be_encrypted->push_back(payload_content_length_header);
         sum_of_payloads_lengths += lengths->at(i);
      }
   }
   // Assemble the payloads into the buffer to be encrypted
   char *encrypted_payloads = NULL;
   apr_size_t encrypted_payloads_size = 0;
   if ((sum_of_payloads_lengths > 0) && (payloads) && (payloads->size() > 0))
   {
      char *buffer_of_all_payloads = (char *)malloc(sizeof(char)*sum_of_payloads_lengths);
      apr_off_t current_position = 0;
      for (int i = 0; i < payloads->size(); i++)
      {
         memcpy(&(buffer_of_all_payloads[current_position]),
                payloads->at(i),
                sizeof(char)*lengths->at(i));
         current_position += lengths->at(i);
      }
      encrypted_payloads = encwrap_content->encryptDataBuffer(buffer_of_all_payloads, sum_of_payloads_lengths, &encrypted_payloads_size);
      free(buffer_of_all_payloads);
   }
   // Get proper content length
   char *true_content_length =
      StringUtils::varprintf("Content-Length: %" APR_SIZE_T_FMT CRLF_STR,
         ((encrypted_payloads) ? encrypted_payloads_size : (apr_size_t)0));
   headers_to_be_encrypted->push_back(true_content_length);
   char *final_header =
      StringUtils::varprintf(CRLF_STR);
   headers_to_be_encrypted->push_back(final_header);
   char *header_data = StringUtils::flattenStringList(headers_to_be_encrypted);
   StringUtils::destroyStringList(headers_to_be_encrypted);
   apr_size_t encrypted_headers_size = 0;
   char *encrypted_headers =
      encwrap_content->encryptDataBuffer(header_data, strlen(header_data), &encrypted_headers_size);
   free(header_data);
   char *plain_header = StringUtils::varprintf(
      "FROM %s" CRLF_STR
      "TO %s" CRLF_STR
      "Content-Length: %" APR_SIZE_T_FMT CRLF_STR CRLF_STR,
      sender, recipient, encrypted_headers_size);
   apr_size_t sentlen = 0;
   int retval = 0;
   sentlen = bb->writeToStream(plain_header, strlen(plain_header));
   if (sentlen != strlen(plain_header))
   {
      errprint("could not send plain headers (%" APR_SIZE_T_FMT "/%" APR_SIZE_T_FMT " bytes sent)", sentlen, (apr_size_t)(strlen(plain_header)));
      retval = 1; goto destroyobjects;
   }
   sentlen = bb->writeToStream(encrypted_headers, encrypted_headers_size);
   if (sentlen != encrypted_headers_size)
   {
      errprint("could not send encrypted headers (%" APR_SIZE_T_FMT "/%" APR_SIZE_T_FMT " bytes sent)", sentlen, encrypted_headers_size);
      retval = 1; goto destroyobjects;
   }
   if (!encrypted_payloads) goto destroyobjects;
   sentlen = bb->writeToStream(encrypted_payloads, encrypted_payloads_size);
   if (sentlen != encrypted_payloads_size)
   {
      errprint("could not send encrypted payloads (%" APR_SIZE_T_FMT "/%" APR_SIZE_T_FMT " bytes sent)", sentlen, encrypted_payloads_size);
      retval = 1; goto destroyobjects;
   }
destroyobjects:
   if (encrypted_payloads) free(encrypted_payloads);
   if (encrypted_headers) free(encrypted_headers);
   if (plain_header) free(plain_header);
   if (encwrap_content) delete encwrap_content;
   return retval;
}

/** Receive the encrypted message by de-serialising from the given
 * bucket brigade
 * 
 * @param bb The bucket brigade to pipe the data in from.
 * 
 * @param kdb The key database.
 * 
 * @param waitinglock The lock to wait on while data is not ready.
 * 
 * @return Non-zero on error, zero on success. */
int BrokerMessage::receiveEncryptedOnBucketBrigade(APRBucketBrigade *bb, KeyDB *kdb, APRMutex *waitinglock)
{
   reset();
   // The plain text portion will never be more than 512 bytes.
   APRTable *plain_headers = HTTPServer::getTable(mp, 512, ": ", CRLF_STR, bb, waitinglock);
   FATAL_IF_NULL(1, plain_headers, "(indicating a corrupt or unauthorised message) plain-text headers");
   FATAL_IF_NULL(1, plain_headers->get("TO"), "TO field");
   FATAL_IF_NULL(1, plain_headers->get("FROM"), "FROM field");
   setRecipient(plain_headers->get("TO"));
   setSender(plain_headers->get("FROM"));
   apr_size_t encrypted_headers_size = HTTPServer::getContentLength(plain_headers);
   apr_size_t encrypted_content_size = 0;
   if (encrypted_headers_size == 0) delete plain_headers;
   FATAL_IF_EXPRESSION(1, (encrypted_headers_size == 0), "zero-length encrypted headers");
   // does this mean that we need mustDestroySendingKey instead?
   const char *tsigner = (!((getSender())[0] == '.')) ? sender : NULL;
   DecryptWrapper *decwrap_content = kdb->getDecrypter(recipientkey, tsigner, true);
   if (decwrap_content == NULL) delete plain_headers;
   FATAL_IF_NULL(1, decwrap_content, "decryption wrapper for the message content");
   char *encrypted_headers_buffer = (char *)malloc(sizeof(char)*encrypted_headers_size);
   char *encrypted_content_buffer = NULL;
   apr_size_t totalbytes_from_stream_as_headers = bb->popFromStreamFull(encrypted_headers_buffer, encrypted_headers_size, waitinglock);
   apr_size_t totalbytes_from_stream_as_content = 0;
   char *decrypted_headers = NULL, *decrypted_content = NULL;
   apr_size_t decrypted_headers_size = 0, decrypted_content_size = 0;
   int retval = 0, enc_headers_trust = 0, enc_content_trust = 0;
   apr_off_t no_of_payloads = 0, current_position = 0;
   if (totalbytes_from_stream_as_headers != encrypted_headers_size)
   {
      errprint("unable to extract the appropriate number of bytes for the encrypted headers (%" APR_SIZE_T_FMT "/%" APR_SIZE_T_FMT " bytes)", totalbytes_from_stream_as_headers, encrypted_headers_size);
      retval = 1; goto destroyobjects2;
   }
   decrypted_headers = decwrap_content->decryptStringBuffer(encrypted_headers_buffer, encrypted_headers_size, &decrypted_headers_size);
   enc_headers_trust = decwrap_content->popMessageTrustedFlag();
   if (decrypted_headers == NULL)
   {
      errprint("decryption failed");
      retval = 1; goto destroyobjects2;
   }
   if ((!enc_headers_trust) && (tsigner))
   {
      errprint("message signature verification failed (headers)");
      retval = 1; goto destroyobjects2;
   }
   parseHeaderStream(decrypted_headers);
   no_of_payloads = (htable->get("Payload-Count")) ? atoi(htable->get("Payload-Count")) : 0;
   encrypted_content_size = HTTPServer::getContentLength(htable);
   if ((no_of_payloads == 0) || (encrypted_content_size == 0))
   {
      retval = 0; goto destroyobjects2;
   }
   encrypted_content_buffer = (char *)malloc(sizeof(char)*encrypted_content_size);
   totalbytes_from_stream_as_content = bb->popFromStreamFull(encrypted_content_buffer, encrypted_content_size, waitinglock);
   if (totalbytes_from_stream_as_content != encrypted_content_size)
   {
      errprint("unable to extract the appropriate number of bytes for the encrypted content (%" APR_SIZE_T_FMT "/%" APR_SIZE_T_FMT " bytes)", totalbytes_from_stream_as_content, encrypted_content_size);
      retval = 1; goto destroyobjects2;
   }
   decrypted_content = decwrap_content->decryptStringBuffer(encrypted_content_buffer, encrypted_content_size, &decrypted_content_size);
   enc_content_trust = decwrap_content->popMessageTrustedFlag();
   if ((!enc_content_trust) && (tsigner))
   {
      errprint("message signature verification failed (content)");
      retval = 1; goto destroyobjects2;
   }
   if (tsigner) setWasSignedFlag();
   current_position = 0;
   for (int i = 0; i < no_of_payloads; i++)
   {
      const char *tpayload_length = htable->get("Payload%d-Content-Length", i);
      if (tpayload_length != NULL)
      {
         int payload_length = atoi(tpayload_length);
         if ((current_position + payload_length) > (decrypted_content_size))
         {
            errprint("content length for payload %d does not make sense (%d + %d > %d)", i,
               (int)current_position, payload_length, (int)(decrypted_content_size));
            reset(); retval = 1; goto destroyobjects2;
         }
         if (payload_length == 0)
            addPayloadBuffer("", payload_length);
         else
            addPayloadBuffer(&(decrypted_content[current_position]), payload_length);
         current_position += atoi(tpayload_length);
      } else
      {
         errprint("content length header on payload number %d does not exist", i);
      }
   }
destroyobjects2:
   if (encrypted_headers_buffer) free(encrypted_headers_buffer);
   if (encrypted_content_buffer) free(encrypted_content_buffer);
   if (decrypted_headers) free(decrypted_headers);
   if (decrypted_content) free(decrypted_content);
   if (decwrap_content) delete decwrap_content;
   delete plain_headers;
   return retval;
}
